package httpd

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"log"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"gitee.com/asphodelus_dev/go-s3-local/util"
	"github.com/gofiber/fiber/v2"
)

type SystemConfig struct {
	Username string
	Password string
}

func RequestLog() fiber.Handler {
	return func(ctx *fiber.Ctx) error {
		log.Println(ctx.Method(), ctx.Request().URI().String())
		return ctx.Next()
	}
}

func S3Interceptor(systemConfig SystemConfig) fiber.Handler {
	return func(c *fiber.Ctx) error {
		method := c.Method()
		path := c.Path()
		log.Printf("methods: %s, path: %s", method, path)

		authorization := c.GetRespHeader("Authorization")
		flag := false

		if authorization != "" {
			flag = validAuthorizationHead(c, systemConfig.Username, systemConfig.Password)
		} else {
			authorization = c.Query("X-Amz-Credential")
			if authorization != "" {
				flag = validAuthorizationUrl(c, systemConfig.Username, systemConfig.Password)
			}
		}

		if !flag {
			return c.SendStatus(http.StatusUnauthorized)
		} else {
			return c.Next()
		}
	}
}

func validAuthorizationUrl(ctx *fiber.Ctx, accessKeyId string, secretAccessKey string) bool {
	requestDate := ctx.Query("X-Amz-Date")
	contentHash := "UNSIGNED-PAYLOAD"
	httpMethod := ctx.Method()
	uri := ctx.Request().URI().String()
	queryString := ctx.Request().URI().QueryArgs().String()

	// 示例
	//"http://localhost:8001/s3/kkk/%E6%B1%9F%E5%AE%81%E8%B4%A2%E6%94%BF%E5%B1%80%E9%A1%B9%E7%9B%AE%E5%AF%B9%E6%8E%A5%E6%96%87%E6%A1%A3.docx?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20230531T024715Z&X-Amz-SignedHeaders=host&X-Amz-Expires=300&X-Amz-Credential=admin%2F20230531%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Signature=038e2ea71073761aa0370215621599649e9228177c332a0a79f784b1a6d9ee39

	// region 参数准备
	// 第一部分-凭证范围
	credential := ctx.Query("X-Amz-Credential")
	credentials := strings.Split(credential, "/")
	accessKey := credentials[0]
	if accessKeyId != accessKey {
		return false
	}
	date := credentials[1]
	region := credentials[2]
	service := credentials[3]
	aws4Request := credentials[4]
	// 第二部分-签名头中包含哪些字段
	signedHeader := ctx.Query("X-Amz-SignedHeaders")
	signedHeaders := strings.Split(signedHeader, ";")
	// 第三部分-生成的签名
	signature := ctx.Query("X-Amz-Signature")

	// 验证 expire
	expires := ctx.Query("X-Amz-Expires")
	expiresInt, err := strconv.Atoi(expires)
	if err != nil {
		log.Println("解析expires失败", err)
		return false
	}
	formattedRequestDate, _ := time.Parse("20060102T150405Z", requestDate)
	startDate := formattedRequestDate.UTC()
	endDate := startDate.Add(time.Second * time.Duration(expiresInt))
	now := time.Now().UTC()
	if endDate.Before(now) {
		log.Printf("超时：time: %v, now: %v", endDate, now)
		return false
	}

	// 待签名字符串
	var stringToSign strings.Builder
	// 签名由4部分组成
	// 1-Algorithm – 用于创建规范请求的哈希的算法。对于 SHA-256，算法是 AWS4-HMAC-SHA256。
	stringToSign.WriteString("AWS4-HMAC-SHA256\n")
	// 2-RequestDateTime – 在凭证范围内使用的日期和时间。
	stringToSign.WriteString(requestDate + "\n")
	// 3-CredentialScope – 凭证范围。这会将生成的签名限制在指定的区域和服务范围内。该字符串采用以下格式：YYYYMMDD/region/service/aws4_request
	stringToSign.WriteString(date + "/" + region + "/" + service + "/" + aws4Request + "\n")
	// 4-HashedCanonicalRequest – 规范请求的哈希。
	// <HTTPMethod>\n
	// <CanonicalURI>\n
	// <CanonicalQueryString>\n
	// <CanonicalHeaders>\n
	// <SignedHeaders>\n
	// <HashedPayload>
	var hashedCanonicalRequest strings.Builder
	// 4.1-HTTP Method
	hashedCanonicalRequest.WriteString(httpMethod + "\n")
	// 4.2-Canonical URI
	hashedCanonicalRequest.WriteString(uri + "\n")
	// 4.3-Canonical Query String
	if queryString != "" {
		queryStringMap, _ := url.ParseQuery(queryString)
		keys := make([]string, 0, len(queryStringMap))
		for k := range queryStringMap {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		var queryStringBuilder strings.Builder
		for _, key := range keys {
			if key != "X-Amz-Signature" {
				queryStringBuilder.WriteString(key + "=" + queryStringMap.Get(key) + "&")
			}
		}
		queryString = strings.TrimSuffix(queryStringBuilder.String(), "&")
		hashedCanonicalRequest.WriteString(queryStringBuilder.String() + "\n")
	} else {
		hashedCanonicalRequest.WriteString(queryString + "\n")
	}
	for _, name := range signedHeaders {
		hashedCanonicalRequest.WriteString(name + ":" + ctx.GetRespHeader(name))
	}
	hashedCanonicalRequest.WriteString("\n")
	hashedCanonicalRequest.WriteString(signedHeader + "\n")
	hashedCanonicalRequest.WriteString(contentHash)
	stringToSign.WriteString(doHex(hashedCanonicalRequest.String()))
	///endregion

	///region 重新生成签名
	//计算签名的key
	kSecret := util.StringToBytes("AWS4" + secretAccessKey)
	kDate := doHmacSHA256(kSecret, date)
	kRegion := doHmacSHA256(kDate, region)
	kService := doHmacSHA256(kRegion, service)
	signatureKey := doHmacSHA256(kService, aws4Request)
	// 计算签名
	authSignature := doHmacSHA256(signatureKey, stringToSign.String())
	// 对签名编码处理
	strHexSignature := doBytesToHex(authSignature)

	if signature == strHexSignature {
		return true
	}
	log.Printf("hex校验失败: sign: %s, strHexSign: %s", signature, strHexSignature)
	return false
}

func validAuthorizationHead(c *fiber.Ctx, accessKeyId string, secretAccessKey string) bool {
	authorization := c.GetRespHeader("Authorization")
	requestDate := c.GetRespHeader("x-amz-date")
	contentHash := c.GetRespHeader("x-amz-content-sha256")
	httpMethod := c.Method()
	uri := c.Request().URI().String()
	queryString := c.Request().URI().QueryArgs().String()
	// log.Println(authorization, requestDate, contentHash)
	// Splitting authorization into parts
	parts := strings.Split(strings.TrimSpace(authorization), ",")
	credential := strings.Split(strings.Split(parts[0], "=")[1], "/")
	accessKey := credential[0]
	// log.Printf("credential: %+v", credential)
	if accessKeyId != accessKey {
		// log.Printf("Authorization校验未通过: id:%s, key:%s\n", accessKeyId, accessKey)
		return false
	}
	date := credential[1]
	region := credential[2]
	service := credential[3]
	aws4Request := credential[4]

	signedHeaders := strings.Split(strings.Split(parts[1], "=")[1], ";")
	signature := strings.Split(strings.Split(parts[2], "=")[1], " ")[0]

	// Constructing the stringToSign
	stringToSign := ""
	stringToSign += "AWS4-HMAC-SHA256\n"
	stringToSign += requestDate + "\n"
	stringToSign += date + "/" + region + "/" + service + "/" + aws4Request + "\n"

	hashedCanonicalRequest := ""
	hashedCanonicalRequest += httpMethod + "\n"
	hashedCanonicalRequest += uri + "\n"
	if queryString != "" {
		queryStringMap, err := parseQueryParams(queryString)
		if err != nil {
			log.Printf("err: %s", err)
			return false
		}

		var keyList []string
		for key := range queryStringMap {
			keyList = append(keyList, key)
		}
		sort.Strings(keyList)

		var queryStringBuilder strings.Builder
		for _, key := range keyList {
			if key != "X-Amz-Signature" {
				queryStringBuilder.WriteString(key + "=" + queryStringMap[key] + "&")
			}
		}
		queryString = strings.TrimRight(queryStringBuilder.String(), "&")

		hashedCanonicalRequest += queryString + "\n"
	} else {
		hashedCanonicalRequest += "\n"
	}
	for _, name := range signedHeaders {
		hashedCanonicalRequest += name + ":" + c.GetRespHeader(name) + "\n"
	}
	hashedCanonicalRequest += "\n"
	hashedCanonicalRequest += strings.Join(signedHeaders, ";") + "\n"
	hashedCanonicalRequest += contentHash

	stringToSign += doHex(hashedCanonicalRequest)

	// Recalculating the signature
	kSecret := util.StringToBytes("AWS4" + secretAccessKey)
	kDate := doHmacSHA256(kSecret, date)
	kRegion := doHmacSHA256(kDate, region)
	kService := doHmacSHA256(kRegion, service)
	signatureKey := doHmacSHA256(kService, aws4Request)
	authSignature := doHmacSHA256(signatureKey, stringToSign)
	strHexSignature := doBytesToHex(authSignature)

	if signature == strHexSignature {
		return true
	}
	log.Printf("签名校验未通过: sign:%s, strHexSign: %s", signature, strHexSignature)
	return false
}

func parseQueryParams(queryString string) (map[string]string, error) {
	queryParams := make(map[string]string)

	params := strings.Split(queryString, "&")
	for _, param := range params {
		keyValue := strings.SplitN(param, "=", 2)
		if len(keyValue) == 2 {
			key, value := keyValue[0], keyValue[1]
			queryParams[key] = value
		} else {
			return nil, errors.New("Invalid query string format")
		}
	}

	return queryParams, nil
}

func doHex(data string) string {
	h := sha256.New()
	h.Write(util.StringToBytes(data))
	return hex.EncodeToString(h.Sum(nil))
}

func doHmacSHA256(key []byte, data string) []byte {
	mac := hmac.New(sha256.New, key)
	mac.Write(util.StringToBytes(data))
	return mac.Sum(nil)
}

func doBytesToHex(data []byte) string {
	return hex.EncodeToString(data)
}
